package com.wutma.friend.service.user.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.wutma.common.core.constants.CacheConstants;
import com.wutma.common.core.constants.Constants;
import com.wutma.common.core.constants.HttpConstants;
import com.wutma.common.core.domain.R;
import com.wutma.common.core.domain.entity.LoginUser;
import com.wutma.common.core.domain.vo.LoginUserVO;
import com.wutma.common.core.enums.ResultCode;
import com.wutma.common.core.enums.UserIdentity;
import com.wutma.common.core.utils.ThreadLocalUtil;
import com.wutma.common.message.service.AliSmsService;
import com.wutma.common.redis.service.RedisService;
import com.wutma.common.security.exception.ServiceException;
import com.wutma.common.security.service.TokenService;
import com.wutma.friend.domain.user.dto.UserDTO;
import com.wutma.friend.domain.user.dto.UserUpdateDTO;
import com.wutma.friend.domain.user.entity.User;
import com.wutma.friend.domain.user.vo.UserVO;
import com.wutma.friend.manager.UserCacheManager;
import com.wutma.friend.mapper.user.UserMapper;
import com.wutma.friend.service.user.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private RedisService redisService;

    @Autowired
    private AliSmsService aliSmsService;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserCacheManager userCacheManager;

    @Value("${sms.code-expiration}")
    private Long phoneCodeExpiration;

    @Value("${sms.send-limit}")
    private Integer sendLimit;

    @Value("${sms.is-send}")
    private boolean isSend; // 开启为true，表示正常启用短信发送功能，默认关闭

    @Value("${jwt.secret}")
    private String secrete;

    @Override
    public boolean sendCode(UserDTO userDTO) {

        if (!checkPhone(userDTO.getPhone())) {
            throw new ServiceException(ResultCode.FAILED_USER_PHONE);
        }

        // 发送验证码后，需等待一分钟后才能重新发送，防止恶意获取
        String phoneCodeKey = getPhoneKey(userDTO.getPhone());
        Long expire = redisService.getExpire(phoneCodeKey, TimeUnit.SECONDS);
        if (expire != null && phoneCodeExpiration * 60 - expire < 60) {
            throw new ServiceException(ResultCode.FAILED_FREQUENT);
        }

        // 限制每人每天最多发送50次验证码，超过当天限制无法继续发送
        String codeSendNumKey = getCodeSendNumKey(userDTO.getPhone());
        Integer codeSendNum = redisService.getCacheObject(codeSendNumKey, Integer.class);
        if (codeSendNum != null && codeSendNum > sendLimit) {
            throw new ServiceException(ResultCode.FAILED_SEND_LIMIT);
        }

        // 向用户手机发送验证码
        String code = isSend ? RandomUtil.randomNumbers(6) : Constants.DEFAULT_CODE;
        redisService.setCacheObject(phoneCodeKey, code, phoneCodeExpiration, TimeUnit.MINUTES);
        if (isSend) {
            boolean sendFlag = aliSmsService.sendMobileCode(userDTO.getPhone(), code);
            if (!sendFlag) {
                throw new ServiceException(ResultCode.FAILED_SEND_CODE);
            }
        }
        redisService.increment(codeSendNumKey);
        // 若代码发送次数为null，表示为当天第一次发送验证码请求，则为从当前时刻开始记录过期时间
        if (codeSendNum == null) {
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
            redisService.expire(codeSendNumKey, seconds, TimeUnit.SECONDS);
        }
        return true;
    }

    @Override
    public String codeLogin(String phone, String code) {

        if (!checkPhone(phone)) {
            throw new ServiceException(ResultCode.FAILED_USER_PHONE);
        }
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));
        String phoneKey = getPhoneKey(phone);
        String cacheCode = redisService.getCacheObject(phoneKey, String.class);
        if (StrUtil.isEmpty(cacheCode)) {
            throw new ServiceException(ResultCode.FAILED_INVALID_CODE);
        }
        if (!cacheCode.equals(code)) {
            throw new ServiceException(ResultCode.FAILED_ERROR_CODE);
        }
        // 核对验证码通过，删除验证码缓存，返回token
        redisService.deleteObject(phoneKey);
        if (user == null) {
            // 为空则表示当前为新用户注册，否则为老用户登录
            user = new User();
            user.setPhone(phone);
            user.setStatus(Constants.TRUE);
            userMapper.insert(user);
        }
        return tokenService.createToken(user.getUserId(), secrete, UserIdentity.ORDINARY.getValue(), user.getNickName(), user.getHeadImage());
    }

    @Override
    public boolean logout(String token) {
        token = getNormalToken(token);
        return tokenService.deleteLoginUser(token, secrete);
    }

    @Override
    public R<LoginUserVO> info(String token) {
        token = getNormalToken(token);
        LoginUser loginUser = tokenService.getLoginUser(token, secrete);
        if (loginUser == null) {
            return R.fail();
        }
        LoginUserVO loginUserVO = new LoginUserVO();
        loginUserVO.setNickName(loginUser.getNickName());
        loginUserVO.setHeadImage(loginUser.getHeadImage());
        return R.ok(loginUserVO);
    }

    @Override
    public R<UserVO> detail() {
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        UserVO userVO = userCacheManager.getUserById(userId);
        if (userVO == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        return R.ok(userVO);
    }

    @Override
    public int edit(UserUpdateDTO userUpdateDTO, String type) {

        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        if (type.equals(Constants.UPDATE_NORMAL)) {
            // 修改用户信息
            updateUserInfo(userUpdateDTO, user);
        }
        if (StrUtil.isNotEmpty(userUpdateDTO.getHeadImage())) {
            user.setHeadImage(userUpdateDTO.getHeadImage());
        }
        // 更新用户缓存：个人信息面板 与 登录信息面板
        userCacheManager.refreshCache(user);
        tokenService.refreshLoginUser(user.getNickName(),user.getHeadImage(),
                ThreadLocalUtil.get(Constants.USER_KEY, String.class));
        // 更新数据库并返回
        return userMapper.updateById(user);
    }

    private static void updateUserInfo(UserUpdateDTO userUpdateDTO, User user) {
        user.setNickName(userUpdateDTO.getNickName());
        user.setSex(userUpdateDTO.getSex());
        user.setSchoolName(userUpdateDTO.getSchoolName());
        user.setMajorName(userUpdateDTO.getMajorName());
        user.setPhone(userUpdateDTO.getPhone());
        user.setEmail(userUpdateDTO.getEmail());
        user.setWechat(userUpdateDTO.getWechat());
        user.setIntroduce(userUpdateDTO.getIntroduce());
    }

    private String getNormalToken(String token) {
        // 去掉令牌Bearer前缀
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        return token;
    }

    private String getPhoneKey(String phone) {
        return CacheConstants.PHONE_CODE_KEY + phone;
    }

    private String getCodeSendNumKey(String phone) {
        return CacheConstants.CODE_NUM_KEY + phone;
    }

    public static boolean checkPhone(String phone) {
        Pattern regex = Pattern.compile("^1[2|3|4|5|6|7|8|9][0-9]\\d{8}$");
        Matcher m = regex.matcher(phone);
        return m.matches();
    }
}
